destroy request on error to avoid hanging sockets#1141
destroy request on error to avoid hanging sockets#1141benzekrimaha wants to merge 5 commits intodevelopment/1.3from
Conversation
On timeout or other errors the socket was left open; destroy the request so the socket is properly closed.
Hello benzekrimaha,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
Request integration branchesWaiting for integration branch creation to be requested by the user. To request integration branches, please comment on this pull request with the following command: Alternatively, the |
Issue: HD-4352
maeldonn
left a comment
There was a problem hiding this comment.
Could you add a test that reproduces the original problem (socket left open on error) and verifies it is now fixed?
Yeah if you are able to add a little test that counts connections or something it's nice, but might be hard I don't know |
| }).on('error', (err) => { | ||
| if (!callbackCalled) { | ||
| callbackCalled = true; | ||
| request.destroy(err); |
There was a problem hiding this comment.
I did a quick check on this, and I think when we reach the error callback of the request, the socket is already destroyed, so maybe here only need to destroy on line 238 ?
There was a problem hiding this comment.
We keep the request.destroy(err)here on purpose:
Node can emit an error after the response callback (e.g., keep‑alive timeout or reset on an already-ended request). In that path the socket is sometimes still in the agent pool; explicitly destroying guarantees the FD is closed and doesn’t leak.
ClientRequest.destroy() is idempotent; if the socket was already torn down, this is a no‑op.
We have regression tests covering the post-response error and stream-error paths to ensure destroy is called and sockets don’t accumulate here: 6d878a0
ce87e45 to
9cc19c9
Compare
Stub http.request to emit a synthetic error after response; assert destroy() is invoked with the expected err.code. Issue: HD-4352
9cc19c9 to
7ab390a
Compare
There was a problem hiding this comment.
The tests cover the post-response error cases well, but they all go through GET which never hits the stream branch in _handleRequest. The change from request.end to request.destroy at line 285 only runs when a stream is piped to the request, and nothing in the tests exercises that path.
It would be good to have a test that passes a stream that errors mid-transfer (e.g. through PUT) and checks that request.destroy gets called.
maeldonn
left a comment
There was a problem hiding this comment.
If @SylvainSenechal comment is right, we're missing the most important test.
9502b41 to
d80775c
Compare
Assert destroy call counts in post-response and stream-error paths to guard against double-destroy regressions. Issue: HD-4352
1df5dd9 to
6d878a0
Compare
maeldonn
left a comment
There was a problem hiding this comment.
LGTM. Just two small comments/questions.
| const afterSockets = countAgentSockets((h as unknown as { httpAgent: unknown }).httpAgent); | ||
| try { | ||
| assert.ok( | ||
| afterSockets <= baselineSockets + 2, |
There was a problem hiding this comment.
When the loop runs, the agent can temporarily have one live socket plus one free socket in its pools (keep‑alive reuse or a race between response and the post‑response. The bound is just to ensure we’re not leaking; it tolerates at most one extra active + one free beyond the baseline.
tests/unit/hdSocketCleanup.spec.ts
Outdated
| }) as typeof http.request); | ||
| } | ||
|
|
||
| function stubRequestCaptureDestroy(assignCapture: (cap: DestroyCapture) => void): void { |
There was a problem hiding this comment.
it seems that there is some duplicated logic between stubRequestCaptureDestroy and stubRequestWithPostResponseError.
There was a problem hiding this comment.
Agreed they share most of the setup. I kept two helpers because one injects a post‑response error and the other only captures destroy, but I can fold them into a single helper with an optional post‑response error injection flag to remove the duplication
Unify destroy-capture stubs with optional post-response error injection to reduce duplication and keep tests clearer. Issue: HD-4352
On timeout or other errors the socket was left open; we now destroy the request so the socket is properly closed.
Issue: HD-4352